Djangoのクラスベースのビューの探訪 Part 3
著者:Leonardo Giordani - 14/02/2014 Updated on Mar 16, 2020
はじめに
この短期連載の最初の 2 回では、Django のクラスベースのビューの基本的な 概念について説明し、Django が用意している基本的な汎用ビューのうちの 2 つを理解して使い始めました。ListView と DetailView です。どちらも、データベースから何らかのデータを読み込んで、レンダリングされたテンプレートに表示するビューです。また、大幅にカスタマイズされたビューを構築できるベースビューや、日付ベースのビューについても簡単に確認しました。
第3回目の今回は、クラスベース版の Django Formを紹介します。この記事は Django の formライブラリを完全に紹介するものではなく、CRUD 操作 (Create,、Read、Update、Delete) の CUD 部分をクラスベースのジェネリックビューがどのように実装しているかを紹介したいと思います。Read 部分は「標準的な」ジェネリックビューが実装しています。
非常に基本的な例
CBF(Class Base Form)を使い始めるにあたり、簡単な例を考えてみましょう。StickyNoteクラスがあり、これは日付の入ったシンプルなテキストノートを表しています。
code: python
class StickyNote(models.Model):
timestamp = models.DateTimeField()
text = models.TextField(blank=True, null=True)
通常、最初にやりたいことの一つは、ユーザーがデータベースに新しいエントリを作成するためのフォームを作ることです。次のようなビューを作成するだけで、新しいノートのデータを入力できるページを作ることができます。
code: python
class NoteAdd(CreateView):
model = StickyNote
このクラスのほとんどが空であることは当然です。最初の2つの記事で標準的なビューで起こったように、継承のおかげで、クラスにはクラス階層のどこかに存在し、舞台裏で動作するコードがたくさん含まれています。私たちの使命は、そのコードを明らかにして、CBF が正確にどのように機能するのか、また、どのように変更すれば必要な機能を果たすことができるのかを理解することです。
この記事をわかりやすくするために、「クラスベースフォーム 」は 「クラスベースフォームビュー 」の略語であることを覚えておいてください。つまり、CBF はビューなので、その仕事は入ってくる HTTP リクエストを処理して HTTP レスポンスを返すことです。フォームビューは、標準的なものとは若干異なる方法でこれを行いますが、これは主にPOSTリクエストがGETリクエストとは異なる性質を持っているためです。次のステップに進む前に、この概念を見てみましょう。
HTTPリクエスト。GETとPOST
このテーマは幅広く、本節では Django の CBF に関連する主な概念を簡単に復習するだけであることに注意してください。
HTTP リクエストは、そのメソッドに応じて様々な形で現れます。これらのメソッドは HTTP 動詞と呼ばれ、最もよく使われるのは GET と POST の 2 つです。GET メソッドは、クライアントがリソース (相対 URL でつながっているもの) を取得したいこと、そして (リソースを変更するなどの) 副作用がないことをサーバに伝えます。POSTメソッドは、サーバーにデータを送信するために使用され、指定されたURLがデータを処理するリソースとなります。
ご覧のように、POSTの定義は非常に幅広く、サーバーは受信したデータを受け入れ、新しいエンティティの作成、1つまたは複数のエンティティの編集または削除など、あらゆる種類のアクションを実行することができます。
フォームはPOSTリクエストと同じものではないことを覚えておいてください。フォームは、HTMLページを閲覧するユーザーからデータを収集するための手段であり、POSTリクエストは、データをサーバーに送信するための手段です。POSTリクエストを行うためにフォームを用意する必要はありません。HTMLフォームはPOSTリクエストを送信するのに便利な手段ですが、それだけではありません。
フォームビュー
なぜフォームビューは標準のビューと違うのでしょうか?その答えは、Webサイトでの典型的なデータ送信の流れを見ればわかります。
ユーザーがWebページを閲覧する(GET)
サーバーがフォームを含むページでGETリクエストに答える
ユーザがフォームを入力して送信する (POST)
サーバーがデータを受け取り、処理する
最初のリクエストではページをGETし、次のリクエストではデータをPOSTするというように、この手順ではサーバーとの二重のやりとりが発生します。そのため、GETリクエストに答えるビューとPOSTリクエストに答えるビューを構築する必要があります。
ほとんどの場合、データをPOSTするために使用するURLは、ページをGETするために使用したURLと同じであるため、両方のメソッドを受け入れるビューを構築する必要があります。Django が提供しているクラスベースのフォームを掘り下げて、この二重のやりとりをどのように処理しているかを理解しましょう。
まず、簡単な例で使った CreateView クラスから始めましょう (CODE)。これはほとんど空のクラスで、SingleObjectTemplateResponseMixin と BaseCreateView を継承しています。最初のクラスは、レスポンスをレンダリングするために選択されたテンプレートを扱うもので、とりあえずは置いておきましょう。一方、2番目のクラス(CODE)は、2つのメソッド(GETとPOST)を実装しており、現在の関心事となっています。 GETリクエストとPOSTリクエストの処理
getメソッドについては、前回の記事でViewクラスのdispatchメソッドについて説明しました。このメソッドは、受信したHTTPリクエストを処理するためのもので、HTTPメソッドがGETの場合に呼び出されます。当然のことながら、POSTメソッドはHTTPリクエストがPOSTの場合に呼び出されます。この2つのメソッドは、BaseCreateViewクラスの祖先であるProcessFormView (CODE)ですでに定義されているので、このクラスのソースコードを見てみるとよいでしょう。 code: python
class ProcessFormView(View):
"""Render a form on GET and processes it on POST."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
ご覧のように2つのメソッドは非常にわかりやすいものですが、フードの下では多くのことが行われていることがわかります。
フォームのワークフロー
まずgetから始めましょう。getは特に何もしません。get_context_dataの結果を渡してrender_to_responseを呼び出すだけなので、テンプレートが何を取得するかを確認するためには、後者を追跡する必要があります。ProcessFormViewやその祖先はget_context_dataというメソッドを提供しておらず、代わりにBaseCreateViewクラスがModelFormMixinから受け取っており、そのModelFormMixinがFormMixin(CODE)から受け取っています。 重要なのは、FormMixinが提供するget_context_dataというメソッドがコンテキストに「フォーム」の値を注入し(CODE)、フォームは同じクラスに定義されているget_formメソッドによって提供され(CODE)、最終的にはform_class属性を使ってフォームをインスタンス化する(CODE)ということなのです。ご覧のようにたくさんのステップがあるので、個人的なソリューションを提供する必要がある場合には、動作をカスタマイズするチャンスがたくさんあります。 しかし、フォーム作成のメカニズムをもっと詳しく見てみるのも面白いかもしれません。なぜなら、ここがGETとPOSTの違いの重要なポイントだからです。get_formメソッドは、フォームクラスを取得すると、それをインスタンス化してフォームを作成します。HTTPメソッドがGETの場合、get_form_kwargs(CODE)は、同じ名前の属性から取得したイニシャルキーとプレフィックスキーを持つ辞書を返します。この記事ではフォームについては触れませんが、BaseForm (CODE)の定義を読むと、その__init__メソッドが同じ2つの属性initalとprefixを受け入れていることがわかります。ModelFormMixin クラスは、使用している Django モデルに関連した命名規則を提供するために、got_form_class と get_form_kwargs の両方の少し複雑なバージョンを注入するので、これは全体のプロセスを単純化したものであることに注意してください。 ProcessFormView の話に戻りますが、post メソッドはテンプレートを直接レンダリングしません。なぜなら、最後のステップを行う前に受信データを処理する必要があるからです。そのため、このメソッドでは get_form を直接呼び出し、その上で検証処理を行い、テストの結果に応じて form_valid か form_invalid を呼び出しています。フォームの検証についての詳細は、公式ドキュメントのForm and field calidation を参照してください。 今回、get_form_kwargs は、フォームがインスタンス化されたときに、2つのキー、すなわち data と files を追加します。これらは、リクエストのPOST属性とFILES属性から直接来ており、ユーザーがサーバーに送信するデータを含んでいます。
最後に、form_valid と form_invalid を見てみましょう。どちらのメソッドもFormMixin(CODE)で提供されていますが、前者はModelFormMixin(CODE)で強化されています。form_invalidの基本バージョンでは、フォーム自体で初期化されたコンテキストデータを渡してrender_to_responseを呼び出します。このようにして、フォームの値と間違ったものに対するエラーメッセージをテンプレートに記入することができます。一方、form_validの基本バージョンでは、単にsuccess_urlへのHttpResponseRedirectを返すだけです。先ほど述べたように、form_validはModelFormMixinでオーバーライドされており、まずフォームを保存してからベース版のメソッドを呼び出します。 ここまでのプロセスを振り返ってみましょう。
URLディスパッチャがフォームを含むページをGETでリクエストします。
ProcessFormViewのgetメソッドは、get_form_classで任意のフォームクラスを見つけます。
フォームクラスは get_form によって self.initial 辞書に含まれる値でインスタンス化されます。
この時点で、テンプレートは通常通りget_context_dataで返されるコンテキストでレンダリングされます。このコンテキストにはフォームが含まれています。
ユーザがフォームを送信すると、URL ディスパッチャーはデータを含む POST でページを要求します。
ProcessFormView の post メソッドはフォームを検証し、データが無効な場合はページを再表示し、データを処理して新たに作成されたオブジェクトを含む成功テンプレートを表示するなどの処理を行います。
更新と削除の操作
このちょっとリッチなコードツアーでは、データベースに新しいオブジェクトを作成するために使用できるCreateViewクラスの内部メカニズムを公開しました。UpdateViewとDeleteViewクラスも同様の経路をたどりますが、実装している異なるアクションを実行するために若干の変更を加えています。
UpdateViewは、フォームがすでに値で満たされている状態を表示したいので、リクエスト(CODE)を処理する前にオブジェクトをインスタンス化します。これにより、モデルフォームがデータを初期化する際に使用するインスタンスキー(CODE)の下、キーワード辞書でオブジェクトを利用できるようになります(CODE)。BaseModelFormのsaveメソッドは、オブジェクトが作成されたのか、それとも変更されただけなのかを理解するのに十分な賢さを持っています(CODEなので、UpdateViewのpostメソッドはCreateViewのものと同じように動作します。
DeleteViewはCreateViewやUpdateViewとは少し違います。公式ドキュメントにあるように、GETメソッドで呼び出された場合、同じURLにPOSTする確認ページを表示します。つまり、GETリクエストの場合、DeleteViewは先祖であるBaseDetailView(CODE)で定義されたgetメソッドを使うだけで、オブジェクトをコンテキストに入れたテンプレートをレンダリングします。POSTリクエストで呼び出された場合、ビューはDeletionMixin(CODE、同じクラス(CODE)のdeleteメソッドを呼び出すだけ)で定義されたpostメソッドを使用します。これにより、データベース上で削除が実行され、成功したURLにリダイレクトされます。
最後に
ご覧のように、Django のクラスベースのフォームビューの現在の実装の背後にある構造は、かなり複雑です。これにより、冒頭の簡単な例で行ったように、いくつかのクラスを定義するだけで、CUD 操作のような複雑な動作を実現することができます。しかし、ほとんどの場合、このような単純化は、クラスの動作に必要な変更を実現する方法をプログラマーが理解するのを難しくします。そこで、私が Django のソースコードの中を大回りした目的は、 HTTP リクエストのライフサイクルの中でどのメソッドが呼び出されているのかを理解し、 オーバーライドする必要のあるメソッドをよりよく特定できるようにすることです。
CUD の標準的な操作から外れた特殊な動作を行う場合は、FormView (CODE) を継承した方が良いでしょう。最初にすべきことは、getとpostメソッドをカスタマイズする必要があるかどうか、どのようにカスタマイズするかをチェックすることです。これらのメソッドの完全な動作を実装するか、変更を加えて親の実装を呼び出す必要があることを覚えておいてください。これらのメソッドの完全な動作を実装するか、変更を加えて親の実装を呼び出す必要があることを覚えておいてください。もし、あなたのアプリケーションに十分でない場合は、get_form_kwargsやform_validなどの専用のメソッドをオーバーライドすることを検討してください。 この記事で、シリーズ「Djangoのクラスベースのビューの探訪」を終了します。
Digging up Django class-based views series
日本語訳:Djangoのクラスベースのビューの探訪 Part 3